#include "settings_manager.h"

#include "tinyxml/tinystr.h"
#include "tinyxml/tinyxml.h"

namespace engine {

std::map<std::string, boost::shared_ptr<Dator> > SettingsManager::settings_;
const std::string SettingsManager::kScopeResolution_ = "::";


int SettingsManager::RegisterVariable(const std::string &name, boost::shared_ptr<Dator> &variable) {  
    if(!variable)
        return base::ER_NULL_POINTER;
    
    settings_[name] = variable;
    return base::OK_OK;
}

int SettingsManager::SetVariable(const std::string & name, const std::string & value) {
    std::map<std::string, boost::shared_ptr<Dator>>::iterator iterator;

    iterator = settings_.find(name);
    if(iterator == settings_.end())
        return engine::ER_SETTING_NOT_FOUND;
    else
        return iterator->second->Set(value);
    
}

int SettingsManager::ParseFile(const std::string & filename) {
    std::string extension(".xml");

    //olny want to test the end of the filename against extension
    //set extension_start to point to just where the extension would start if there was one
    size_t extension_start = filename.length() - extension.length();
    
    if(filename.find(extension, extension_start) != std::string::npos)
        return ParseXmlFile(filename);
    else return ParseIniFile(filename);
}

int SettingsManager::ParseXmlFile(const std::string &filename) {
    TiXmlDocument document(filename.c_str());

    bool load_ok = document.LoadFile();

    if(load_ok)
    {
        TiXmlElement * element;
        element = document.RootElement();
        element = element->FirstChildElement();

        int result = base::OK_OK;
        while(element) {
            result |= ParseXmlElement(element, std::string());
            element = element->NextSiblingElement();
        }
        return result;
    }
    else return base::ER_FAIL_TO_READ_FILE;
}


int SettingsManager::ParseIniFile(const std::string &filename) {
    std::ifstream file_stream(filename.c_str());
  
    if(!file_stream.is_open())
        return base::ER_FAIL_TO_READ_FILE;

    std::string path;
    int result = base::OK_OK;
    while(!file_stream.eof())
    {
        char szBuf[1024];        
        file_stream.getline(szBuf,1024);
        result |= ParseIniLine(std::string(szBuf), path);
    }    

    return result;
}

  
int SettingsManager::AppendGroupName(const std::string &group_name, std::string &path) {
    size_t path_length = path.length();
    if(path_length != 0) {
        //there should be a kScopeResolution_ string match right at the end of path
        //so point scope_start to just before the end of path
        size_t scope_start = path_length - kScopeResolution_.length();

        //and try to find a match to kScopeResolution_
        scope_start = path.find(kScopeResolution_, scope_start);

        //If no match is found, append path with a kScopeResolution_
        if(scope_start == std::string::npos)
            path += kScopeResolution_;
    }

    //Now append path with the group name, and one more kScopeReolution_
    path += group_name + kScopeResolution_;

    return base::OK_OK;
}

int SettingsManager::RemoveLastGroup(std::string & path)
{
    //remove the last group name and the corresponding ::
    //NOTE: if no :: is found in path, 
    //the new path will be the empty string
    
    //search for second to last ::
    //example:
    //  for path = group1::group2::
    //  find the :: just after group1
    //The last position we want to consider in the search is just before the last ::
    size_t end_position = path.length() - kScopeResolution_.length() - 1;
    end_position = path.rfind(kScopeResolution_, end_position);

    //check to see whether :: was found
    if(end_position != std::string::npos) {
        end_position += kScopeResolution_.length();
        path = path.substr(0, end_position);
        return base::OK_TRUE;
    }
    else {
        path.clear();    
        return base::OK_FALSE;
    }
}

int SettingsManager::ResolveName(const std::string & path, std::string & name) {
    
    size_t path_length = path.length();
    if(path_length != 0) {
        //there should be a kScopeResolution_ string match right at the end of path
        //so point scope_start to just before the end of path
        size_t scope_start = path_length - kScopeResolution_.length();

        //and try to find a match to kScopeResolution_
        scope_start = path.find(kScopeResolution_, scope_start);

        //If no match is found, prefix the name with a kScopeResolution_
        if(scope_start == std::string::npos)
            name = kScopeResolution_ + name;
    }

    //Now resolve the name by appending it to the path
    name = path + name;

    return base::OK_OK;
}

  
int SettingsManager::ParseIniLine(const std::string & line, std::string & path) {    
    size_t line_length = line.length();

    if(line_length == 0) 
        return base::OK_OK; //empty line. ditch out early

    size_t end_whitespace = line.find_first_not_of(' ');
    if(end_whitespace == std::string::npos)
        return base::OK_OK; //nothing but whitespace, ditch out early

    size_t equals_operator = line.find('=', end_whitespace);
    
    //If an equals operator is present, then we are setting a variable's value
    if( equals_operator != std::string::npos) {
        size_t name_start, name_end;
        size_t value_start, value_end;
    
        std::string name, value;
        
        name_start = line.find_first_not_of(' ', end_whitespace);
        name_end = line.find_last_not_of(' ', equals_operator - 1) + 1;
        value_start = line.find_first_not_of(' ', equals_operator + 1);
        value_end = line.find_last_not_of(' ') + 1;

        name = line.substr(name_start, name_end - name_start);
        value = line.substr(value_start, value_end - value_start);

        ResolveName(path, name);
        
        int result = SetVariable(name, value);
        if( result == engine::ER_SETTING_NOT_FOUND )
            return base::OK_FALSE;
        else return base::OK_TRUE;
    }
    //No equality operator, so check if maybe this is the start or end of a group of settings
    else {
        size_t open_bracket, close_bracket;
        open_bracket = line.find_first_of('[');
        close_bracket = line.find_last_of(']');

        //Check for proper syntax.
        //There must be an open and close bracket present;
        if(open_bracket != std::string::npos && close_bracket !=std::string::npos)
        {
            size_t name_start;
            name_start = line.find_first_not_of(' ', open_bracket + 1);            

            //make sure that there is some name given to this new group
            //that is that there is some non-whitespace character between the open and close brackets
            if(name_start != close_bracket) {                
                //Some group name is given.
                //trim off any whitespace before and after the name;               
                size_t name_end;                                
                name_end = line.find_last_not_of(' ', close_bracket - 1) + 1;
                
                //I now have the start and end of the group name, so grab it out of the line
                std::string group_name = line.substr(name_start, name_end - name_start);

                //now add the group name to the current name_scope;
                return AppendGroupName(group_name, path);                
            } else {      
                //The first non-whitespace character IS the closing braket,
                //then we have no name.
                //use this syntax to indicate the end of the group
                int result_code = RemoveLastGroup(path);
                if(result_code::IsSuccess(result_code))
                    return base::OK_OK;     
                else return result_code;
            }
        }else return base::ER_FILE_FORMAT;
    }

}

int SettingsManager::ParseXmlElement(const TiXmlElement *element, std::string path) {  
    if(!element)
        return base::ER_NULL_POINTER;

    std::string name(element->Value());
    if(!element->FirstChildElement()) {        
        std::string value(element->GetText());

        ResolveName(path, name);
        
        int result = SetVariable(name, value);
        if( result == engine::ER_SETTING_NOT_FOUND )
            return base::OK_FALSE;
        else return base::OK_TRUE;
    } else {
        const TiXmlElement * child = element->FirstChildElement();
        AppendGroupName(name, path);

        int result = 0;
        while(child) {
            result |= ParseXmlElement(child, path);
            child = child->NextSiblingElement();
        }

        return result;
    }
}


 

}//namespace engine